//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.xa
{

	using XAException = javax.transaction.xa.XAException;
	using XAResource = javax.transaction.xa.XAResource;

	using PropertyIndex = org.neo4j.kernel.impl.core.PropertyIndex;
	using NeoStore = org.neo4j.kernel.impl.nioneo.store.NeoStore;
	using NodeStore = org.neo4j.kernel.impl.nioneo.store.NodeStore;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using PropertyStore = org.neo4j.kernel.impl.nioneo.store.PropertyStore;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using RelationshipData = org.neo4j.kernel.impl.nioneo.store.RelationshipData;
	using RelationshipStore = org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using RelationshipTypeStore = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;
	using XaConnectionHelpImpl = org.neo4j.kernel.impl.transaction.xaframework.XaConnectionHelpImpl;
	using XaResourceHelpImpl = org.neo4j.kernel.impl.transaction.xaframework.XaResourceHelpImpl;
	using XaResourceManager = org.neo4j.kernel.impl.transaction.xaframework.XaResourceManager;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

///
/// <summary> * <seealso cref="XaConnection"/> implementation for the Neo4j kernel native store. Contains
/// * getter methods for the different stores (node,relationship,property and
/// * relationship type).
/// * <p>
/// * A <CODE>NeoStoreXaConnection</CODE> is obtained from
/// * <seealso cref="NeoStoreXaDataSource"/> and then Neo4j persistence layer can perform the
/// * operations requested via the store implementations. </summary>
/// 
	public class NeoStoreXaConnection : XaConnectionHelpImpl
	{
		private readonly NeoStoreXaResource xaResource;

		private readonly NeoStore neoStore;
		private readonly NodeEventConsumer nodeConsumer;
		private readonly RelationshipEventConsumer relConsumer;
		private readonly RelationshipTypeEventConsumer relTypeConsumer;
		private readonly PropertyIndexEventConsumer propIndexConsumer;

		private NeoTransaction neoTransaction = null;

		internal NeoStoreXaConnection(NeoStore neoStore, XaResourceManager xaRm, sbyte[] branchId) : base(xaRm)
		{
			this.neoStore = neoStore;

			this.nodeConsumer = new NodeEventConsumerImpl(this);
			this.relConsumer = new RelationshipEventConsumerImpl(this);
			this.relTypeConsumer = new RelationshipTypeEventConsumerImpl(this);
			this.propIndexConsumer = new PropertyIndexEventConsumerImpl(this);
			this.xaResource = new NeoStoreXaResource(neoStore.getStorageFileName(), xaRm, branchId);
		}

///    
///     <summary> * Returns this the <seealso cref="NodeStore"/>.
///     *  </summary>
///     * <returns> The node store </returns>
///     
		public virtual NodeEventConsumer getNodeConsumer()
		{
			return nodeConsumer;
		}

///    
///     <summary> * Returns this the <seealso cref="RelationshipStore"/>.
///     *  </summary>
///     * <returns> The relationship store </returns>
///     
		public virtual RelationshipEventConsumer getRelationshipConsumer()
		{
			return relConsumer;
		}

		public virtual PropertyIndexEventConsumer getPropertyIndexConsumer()
		{
			return propIndexConsumer;
		}

///    
///     <summary> * Returns this the <seealso cref="RelationshipTypeStore"/>.
///     *  </summary>
///     * <returns> The relationship type store </returns>
///     
		public virtual RelationshipTypeEventConsumer getRelationshipTypeConsumer()
		{
			return relTypeConsumer;
		}

///    
///     <summary> * Made public for testing, dont use. </summary>
///     
		public virtual PropertyStore getPropertyStore()
		{
			return neoStore.getPropertyStore();
		}

		internal virtual NodeStore getNodeStore()
		{
			return neoStore.getNodeStore();
		}

		internal virtual RelationshipStore getRelationshipStore()
		{
			return neoStore.getRelationshipStore();
		}

		internal virtual RelationshipTypeStore getRelationshipTypeStore()
		{
			return neoStore.getRelationshipTypeStore();
		}

		public override XAResource getXaResource()
		{
			return this.xaResource;
		}

		internal virtual NeoTransaction getNeoTransaction()
		{
			if (neoTransaction != null)
			{
				return neoTransaction;
			}
			try
			{
				neoTransaction = (NeoTransaction) getTransaction();
				return neoTransaction;
			}
			catch (XAException e)
			{
				throw new TransactionFailureException("Unable to get transaction.", e);
			}
		}

		private class NeoStoreXaResource : XaResourceHelpImpl
		{
			private readonly object identifier;

			internal NeoStoreXaResource(object identifier, XaResourceManager xaRm, sbyte[] branchId) : base(xaRm, branchId)
			{
				this.identifier = identifier;
			}

			public override bool isSameRM(XAResource xares)
			{
				if (xares is NeoStoreXaResource)
				{
					return identifier.Equals(((NeoStoreXaResource) xares).identifier);
				}
				return false;
			}

		}

		private class NodeEventConsumerImpl : NodeEventConsumer
		{
			private readonly NeoStoreXaConnection xaCon;

			public NodeEventConsumerImpl(NeoStoreXaConnection xaCon)
			{
				this.xaCon = xaCon;
			}

			public virtual void createNode(int nodeId)
			{
				xaCon.getNeoTransaction().nodeCreate(nodeId);
			}

			public virtual void deleteNode(int nodeId)
			{
				xaCon.getNeoTransaction().nodeDelete(nodeId);
			}

		// checks for created in tx else get from store
			public virtual bool loadLightNode(int nodeId)
			{
				return xaCon.getNeoTransaction().nodeLoadLight(nodeId);
			}

			public virtual void addProperty(int nodeId, int propertyId, PropertyIndex index, object @value)
			{
				xaCon.getNeoTransaction().nodeAddProperty(nodeId, propertyId, index, @value);
			}

			public virtual void changeProperty(int nodeId, int propertyId, object @value)
			{
				xaCon.getNeoTransaction().nodeChangeProperty(nodeId, propertyId, @value);
			}

			public virtual void removeProperty(int nodeId, int propertyId)
			{
				xaCon.getNeoTransaction().nodeRemoveProperty(nodeId, propertyId);
			}

			public virtual ArrayMap<int, PropertyData> getProperties(int nodeId)
			{
				return xaCon.getNeoTransaction().nodeGetProperties(nodeId);
			}
		}

		private class RelationshipEventConsumerImpl : RelationshipEventConsumer
		{
			private readonly NeoStoreXaConnection xaCon;

			public RelationshipEventConsumerImpl(NeoStoreXaConnection xaCon)
			{
				this.xaCon = xaCon;
			}

			public virtual void createRelationship(int id, int firstNode, int secondNode, int type)
			{
				xaCon.getNeoTransaction().relationshipCreate(id, firstNode, secondNode, type);
			}

			public virtual void deleteRelationship(int id)
			{
				xaCon.getNeoTransaction().relDelete(id);
			}

			public virtual void addProperty(int relId, int propertyId, PropertyIndex index, object @value)
			{
				xaCon.getNeoTransaction().relAddProperty(relId, propertyId, index, @value);
			}

			public virtual void changeProperty(int relId, int propertyId, object @value)
			{
				xaCon.getNeoTransaction().relChangeProperty(relId, propertyId, @value);
			}

			public virtual void removeProperty(int relId, int propertyId)
			{
				xaCon.getNeoTransaction().relRemoveProperty(relId, propertyId);
			}

			public virtual ArrayMap<int, PropertyData> getProperties(int relId)
			{
				return xaCon.getNeoTransaction().relGetProperties(relId);
			}

			public virtual RelationshipData getRelationship(int id)
			{
				return xaCon.getNeoTransaction().relationshipLoad(id);
			}

			public virtual RelationshipChainPosition getRelationshipChainPosition(int nodeId)
			{
				return xaCon.getNeoTransaction().getRelationshipChainPosition(nodeId);
			}

			public virtual IEnumerable <RelationshipData> getMoreRelationships(int nodeId, RelationshipChainPosition position)
			{
				return xaCon.getNeoTransaction().getMoreRelationships(nodeId, position);
			}
		}

		private class RelationshipTypeEventConsumerImpl : RelationshipTypeEventConsumer
		{
			private readonly NeoStoreXaConnection xaCon;
			private readonly RelationshipTypeStore relTypeStore;

			internal RelationshipTypeEventConsumerImpl(NeoStoreXaConnection xaCon)
			{
				this.xaCon = xaCon;
				this.relTypeStore = xaCon.getRelationshipTypeStore();
			}

			public virtual void addRelationshipType(int id, string name)
			{
				xaCon.getNeoTransaction().relationshipTypeAdd(id, name);
			}

			public virtual RelationshipTypeData getRelationshipType(int id)
			{
				return relTypeStore.getRelationshipType(id);
			}

			public virtual RelationshipTypeData[] getRelationshipTypes()
			{
				return relTypeStore.getRelationshipTypes();
			}
		}

		private class PropertyIndexEventConsumerImpl : PropertyIndexEventConsumer
		{
			private readonly NeoStoreXaConnection xaCon;

			internal PropertyIndexEventConsumerImpl(NeoStoreXaConnection xaCon)
			{
				this.xaCon = xaCon;
			}

			public virtual void createPropertyIndex(int id, string key)
			{
				xaCon.getNeoTransaction().createPropertyIndex(id, key);
			}

			public virtual string getKeyFor(int id)
			{
				return xaCon.getNeoTransaction().getPropertyIndex(id);
			}

			public virtual PropertyIndexData[] getPropertyIndexes(int count)
			{
				return xaCon.getNeoTransaction().getPropertyIndexes(count);
			}
		}
	}
}